package xj.graph2d.app.project;

import java.io.*;
import java.util.Date;
import java.util.Iterator;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;

import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import xj.app.Application;
import xj.graph2d.Document;
import xj.graph2d.DocumentException;
import xj.graph2d.app.DrawApp;
import xj.lang.CodeWriter;
import xj.util.xml.ErrorMessageHandler;
import xj.util.xml.XMLUtil;
import xj.util.StringUtils;

public class Project 
  extends ProjectFolder {

  public static final String PROJECT_FILE_SUFFIX = "vmp";

  public static final String PROJECT_FILE_DESCRIPTION = "Visual modeler project files (*.vmp)";

  public static final String PROJECT_ARCHIVE_SUFFIX = "par";

  public static final String PROJECT_ARCHIVE_DESCRIPTION = "Visual modeler project archive files (*.par)";

  public Project(Application app) {
    creationTime = System.currentTimeMillis();
    this.app = app;
  }

  public Project(Application app, String name) {
    this(app, name, ProjectType.Default);
  }

  public Project(Application app, String name, ProjectType type) {
    super(name);
    creationTime = System.currentTimeMillis();
    this.app = app;
    if (type == null) {
      type = ProjectType.Default;
    }
    setProjectType(type);
  }

  public Application getApplication() {
    return app;
  }

  public ProjectType getProjectType() {
    return projectType;
  }

  public void setProjectType(ProjectType type) {
    this.projectType = type;
  }

  public String getType() { 
    ProjectType type = getProjectType();
    if (type != null) {
      return StringUtils.humanize(type.toString());
    }	
    return null;
  }

  public Object getModel() {
    return model;
  }

  public void setModel(Object model) {
    this.model = model;
  }

  public String getWorkingDir() {
    return workingDir;
  }

  public void setWorkingDir(String workingDir) {
    this.workingDir = workingDir;
  }

  public String getDefaultDir() {
    return defaultDir;
  }

  public void setDefaultDir(String defaultDir) {
    this.defaultDir = defaultDir;
  }

  public void setDefaultDir() {
    if (app != null) {
      this.defaultDir = app.getDefaultDir();
    }
  }

  public String getProjectPath() {
    return projectPath;
  }

  public void setProjectPath(String path) {
    projectPath = path;
    if (path != null) {
      int i = path.lastIndexOf(File.separator);
      if (i >= 0) {
	workingDir = path.substring(0, i);
	projectPath = path.substring(i + 1);
      }
    }
  }

  public ProjectItem getCurrentItem() {
    return currentItem;
  }

  public void setCurrentItem(ProjectItem currentItem) {
    this.currentItem = currentItem;
  }

  public long getCreationTime() {
    return creationTime;
  }

  public void saveProject() 
    throws IOException, DocumentException {
    if (app != null) {
      saveAllDocuments();

      if (debug) {
	System.out.println("Project.saveProject()");
      }

      try {
	if (name != null) {
	  File file = getProjectFile(name);
	  File dir = file.getParentFile();
	  createDir(dir);

	  if (debug) {
	    System.out.println("Project.saveProject(): " + file.getAbsolutePath()); // getName());
	  }

	  PrintWriter pout = new PrintWriter(new BufferedWriter(new FileWriter(file)));
	  CodeWriter out = new CodeWriter(pout);
	  out.setIndentSize(1);
	  String appName = app.getAppName();
	  String version = app.getVersion();
	  String rdate = app.getReleaseDate();
	  String namespaceURI = app.getNamespaceURI();
	  String namespacePrefix = app.getNamespacePrefix();

	  out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>");
	  if (appName != null) {
	    out.println("<!-- Created by " + appName + " " + (new Date()) + " -->");
	  }

	  String rootname = getPrefixedName(TAG_NAME_PROJECT, useDefaultNamespace ? null : namespacePrefix);
	  if (version == null) {
	    version = "4.0";
	  }
	  if (rdate != null) {
	    version += ("/" + rdate);
	  }

	  out.print("<" + rootname);
	  if (namespaceURI != null && 
	      namespaceURI.length() > 0) {
	    out.print(" xmlns");
	    if (!useDefaultNamespace && 
		namespacePrefix != null	&& 
		namespacePrefix.length() > 0) {
	      out.print(":" + namespacePrefix);
	    }
	    out.print("=\"" + namespaceURI + "\"");
	  }
	  out.println(" version=\"" + version + "\"");
	  //out.print("      name=\"" + name + "\"");
	  out.println("      type=\"" + projectType + "\">");
	  out.thisIndentInc();

	  long t = System.currentTimeMillis();
	  String tsname = getPrefixedName(TAG_NAME_TIMESTAMP, useDefaultNamespace ? null : namespacePrefix);
	  out.print("<" + tsname + " t=\"" + t + "\">");
	  out.print(xj.util.xml.XSLTUtil.filterXMLText((new Date(t)).toString()));
	  out.println("</" + tsname + ">");

	  writeItemInfo(out, useDefaultNamespace ? null : namespacePrefix);
	  writeItemsXML(out, useDefaultNamespace ? null : namespacePrefix);

	  out.thisIndentDec();
	  out.println("</" + rootname + ">");
	  out.close();
	}
	changed = false;
      } catch (IOException e) {
	System.err.println(e.getMessage());
      }
    }
  }

  public static String getPrefixedName(String name, String namespacePrefix) {
    if (namespacePrefix != null && namespacePrefix.length() > 0) {
      return namespacePrefix + ":" + name;
    } else {
      return name;
    }
  }

  public static void createDir(File dir) {
    if (dir != null && 
	!dir.exists()) {
      dir.mkdirs();
    }
  }

  /**
   * Return the relative path of the specified path wrt the base.
   */
  public static String getRelativePath(String base, String path) {
    if (base != null && path != null) {
      if (!base.endsWith(File.separator)) {
	base = (base + File.separator);
      }
      if (path.startsWith(base)) {
	return path.substring(base.length());
      }
    }
    return path;
  }

  public void loadProject(String name) 
    throws IOException, ProjectException, DocumentException {
    if (name != null) {
      File file = getProjectFile(name);
      InputStream in = new FileInputStream(file);
      loadProject(in);
    }
  }

  public void loadProjectFromPath(String path) 
    throws IOException, ProjectException, DocumentException {
    if (path != null) {
      File f = new File(path);
      if (!f.isAbsolute() && workingDir != null) {
	path = workingDir + File.separator + path;
      }

      String projectName = path; 
      int i = projectName.lastIndexOf('.'); 
      if (i >= 0) { 
	projectName = projectName.substring(0, i); 
	i = projectName.lastIndexOf(File.separator);
	if (i >= 0) { 
	  projectName = projectName.substring(i + 1);
	}
      }
      setName(projectName);
      InputStream in = new FileInputStream(path);
      loadProject(in);
      setProjectPath(path);
    }
  }

  public void loadProject(InputStream in) 
    throws ProjectException, IOException, DocumentException {
    if (in != null) {
      try {
	ErrorMessageHandler errorHandler = new ErrorMessageHandler(name);
	org.w3c.dom.Document projdoc = XMLUtil.parseXMLDocument(in, errorHandler, false);
	if (projdoc != null) {
	  Element docElement = projdoc.getDocumentElement();
	  XMLUtil.stripWhiteSpace(docElement);
	  String namespaceURI = app.getNamespaceURI();

	  if (TAG_NAME_PROJECT.equals(docElement.getLocalName()) && 
	      namespaceURI.equals(docElement.getNamespaceURI())) {
	    //name = docElement.getAttribute("name");
	    String projType = docElement.getAttribute("type");
	    projectType = ProjectType.toProjectType(projType);
	    
	    loadItemsXML(docElement, namespaceURI);
	  } else {
	    // System.err.println("Error loading project " + name +
	    // ": wrong namespace");
	    throw new ProjectException("Error in loading project " + name
				       + ": wrong namespace");
	  }
	}
      } catch (IOException e) {
	throw new ProjectException("Unable to load project " + name + ": "
				   + e.getMessage(), e);
      } catch (SAXParseException e) {
	// System.err.println("Error in loading project " + name + ":" +
	// e.getMessage());
	if (debug) {
	  e.printStackTrace(System.err);
	}
	throw new ProjectException("Error in loading project " + name + ": "
				   + e.getMessage(), e);
      } catch (SAXException e) {
	// System.err.println("Error in loading project " + name + ":" +
	// e.getMessage());
	if (debug) {
	  e.printStackTrace(System.err);
	}
	throw new ProjectException("Error in loading project " + name + ":"
				   + e.getMessage(), e);
      }
    }
  }

  /**
   * Create an archive that contains all the files of the project
   */
  public void saveProjectArchive(String archiveName) 
    throws IOException, DocumentException {
    saveProject();
    try {
      if (archiveName == null) {
	archiveName = name;
      }
      File arcfile = getProjectArchiveFile(archiveName);
      FileOutputStream fout = new FileOutputStream(arcfile);
      JarOutputStream out = new JarOutputStream(fout);

      String projFilename = getProjectFilename();
      //JarEntry je = new JarEntry(projFilename);
      JarEntry je = new JarEntry("Project.vmp");
      // je.setComment("Project");
      out.putNextEntry(je);
      File projfile = getProjectFile(name);
      BufferedInputStream in = new BufferedInputStream(new FileInputStream(projfile));
      int c;
      while ((c = in.read()) != -1) {
	out.write(c);
      }
      in.close();
      out.closeEntry();

      Iterator<ProjectItem> iter = allItems();
      while (iter.hasNext()) {
	archive(iter.next(), out);
      }

      out.close();

      // System.out.println("saveProjectArchive: " + arcfile);
    } catch (IOException e) {
      System.err.println(e.getMessage());
    }
  }

  protected void archive(ProjectItem item, JarOutputStream out)
      throws IOException {
    if (item != null && out != null) {
      if (item instanceof ProjectFolder) {
	ProjectFolder folder = (ProjectFolder) item;
	Iterator<ProjectItem> iter = folder.allItems();
	while (iter.hasNext()) {
	  archive(iter.next(), out);
	}
      } else {
	File file = item.getDocumentFile();
	if (file != null) {
	  BufferedInputStream in = new BufferedInputStream(new FileInputStream(file));
	  String path = item.getDocumentPath();
	  int i = path.indexOf(File.separator);
	  if (i > 0) { 
	    path = path.substring(i + 1);
	  }
	  path = path.replace(File.separatorChar, '/');
	  out.putNextEntry(new JarEntry(path)); 
	  int c;
	  while ((c = in.read()) != -1) {
	    out.write(c);
	  }
	  in.close();
	  out.closeEntry();
	}
      }
    }
  }

  public void loadProjectArchive(String archiveName) 
    throws IOException, ProjectException, DocumentException {
    if (archiveName != null) {
      File file = new File(archiveName);
      String dir = null;
      if (file.isAbsolute()) {
	dir = file.getParentFile().toString();
      } else {
	if (workingDir != null) {
	  dir = workingDir;
	} else if (defaultDir != null) {
	  dir = defaultDir;
	}
      }

      String projectName = archiveName; 
      int i = projectName.lastIndexOf('.'); 
      if (i >= 0) { 
	projectName = projectName.substring(0, i); 
	i = projectName.lastIndexOf(File.separator);
	if (i >= 0) { 
	  projectName = projectName.substring(i + 1);
	}
      }

      String projectPath = null;

      FileInputStream fin = new FileInputStream(archiveName);
      JarInputStream in = new JarInputStream(new BufferedInputStream(fin));
      JarEntry je;
      while ((je = in.getNextJarEntry()) != null) {
	String name = je.getName();
	//name = name.replaceAll("/", File.separator); 
	name = name.replace('/', File.separatorChar); 

	String filename;
	if (name.endsWith(".vmp")) {
	  filename = projectName + ".vmp";
	  projectPath = filename;	  
	  if (dir != null) { 
	    filename =  dir + File.separator + filename; 
	  }
	} else { 
	  if (dir != null) {
	    filename = dir + File.separator + projectName + File.separator + name;
	  } else {
	    filename = name;
	  }
	}

	if (debug) { 
	  System.out.println("Project.loadProjectArchive() archiveName=" + archiveName + 
			     " dir=" + dir + " projectName=" + projectName +
			     " filename=" + filename);
	}

	File f = new File(filename);
	File pdir = f.getParentFile();
	createDir(pdir);

	BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(filename));
	int c;
	while ((c = in.read()) != -1) {
	  out.write(c);
	}
	out.close();
      }
      in.close();

      // System.out.println("Project.loadProjectArchive() projectPath=" +
      // projectPath);

      setName(projectName); 
      if (projectPath != null) {
	loadProjectFromPath(projectPath);
      }
    }
  }

  public String getProjectFilename() {
    String filename = null;
    if (projectPath != null) {
      filename = projectPath;
    } else {
      filename = name + "." + PROJECT_FILE_SUFFIX;
    }
    return filename;
  }

  protected File getProjectFile(String name) 
    throws IOException {
    if (name != null) {
      String dir = null;
      if (workingDir != null) {
	dir = workingDir;
      } else {
	if (defaultDir == null) {
	  setDefaultDir();
	}
	if (defaultDir != null) {
	  workingDir = defaultDir;
	}
      }
      String fullpath;
      String filename = name + "." + PROJECT_FILE_SUFFIX;
      /* 
      if (projectPath != null) {
	filename = projectPath;
      } else {
	filename = name + "." + PROJECT_FILE_SUFFIX;
      }
      */
      if (dir != null) {
	fullpath = dir + File.separator + filename;
      } else {
	fullpath = filename;
      }
      return new File(fullpath);
    }
    return null;
  }

  protected File getProjectArchiveFile(String name) throws IOException {
    if (name != null) {
      File file = new File(name);
      if (file.isAbsolute()) {
	return file;
      }

      String fullpath;
      String dir = null;
      if (workingDir != null) {
	dir = workingDir;
      } else {
	if (defaultDir == null) {
	  setDefaultDir();
	}
	if (defaultDir != null) {
	  workingDir = defaultDir;
	}
      }
      String filename = name;
      if (!filename.endsWith("." + PROJECT_ARCHIVE_SUFFIX)) {
	filename = filename + "." + PROJECT_ARCHIVE_SUFFIX;
      }
      if (dir != null) {
	fullpath = dir + File.separator + filename;
      } else {
	fullpath = filename;
      }
      return new File(fullpath);
    }
    return null;
  }

  public String getDocumentExtension(Document.DocumentType type) {
    if (app instanceof DrawApp) {
      DrawApp application = (DrawApp) app;
      return application.getDocumentExtension(type);
    } else {
      return "xml";
    }
  }

  public String getDefaultItemName() {
    switch (projectType) { 
    case VisualProgram:
      return "Program";
    case Default:
      return "Diagram";
    case UML:
    default:
      return "ClassDiagram";
    }
  }

  public Document.DocumentType getDefaultItemType() {
    switch (projectType) { 
    case VisualProgram:
      return Document.DocumentType.Visual_Class_Diagram;
    case Default:
      return Document.DocumentType.Generic_Diagram;
    case UML:
    default:
      return Document.DocumentType.UML_Class_Diagram;
    }
  }

  public OpenDocumentListener getOpenDocumentListener() {
    return openListener;
  }

  public void setOpenDocumentListener(OpenDocumentListener l) {
    openListener = l;
  }

  protected static final String TAG_NAME_PROJECT = "Project";

  protected static final String TAG_NAME_PROJECT_ITEM = "Item";

  protected static final String TAG_NAME_PROJECT_FOLDER = "Folder";

  protected static final String TAG_NAME_DOCUMENT = "Document";

  protected static final String TAG_NAME_TIMESTAMP = "TimeStamp";

  protected static boolean useDefaultNamespace = true;

  protected Application app;

  protected Object model;

  protected ProjectType projectType = ProjectType.Default;

  protected long creationTime;

  protected String workingDir = null;

  protected String defaultDir = null;

  protected String projectPath = null;

  protected boolean modified;

  protected ProjectItem currentItem;

  protected OpenDocumentListener openListener;

  protected static final boolean debug = false;

}
